#include <pgn.h>
#include <game.h>
#include <san.h>

#include <stdlib.h>
#include <string.h>
#include <ctype.h>


enum pgn_token_t {
  pt_left_bracket,
  pt_right_bracket,
  pt_left_par,
  pt_right_par,
  pt_left_angle,
  pt_right_angle,
  pt_dot,
  pt_asterisk,
  pt_string,
  pt_integer,
  pt_symbol,
  pt_nag,
  pt_eof,
  pt_error
};


int pgn_print(FILE* f, const struct game_t* g)
{
  int n = game_length(g);
  move_t* moves = (move_t*)malloc(sizeof(move_t) * n);
  if (moves == 0)
    return 0;
  n = game_moves(g, moves, n);

  char buf[MAX_SAN_LEN];
  int line = 0;
  game_t game;
  if (!game_init(&game)) {
    free(moves);
    return 0;
  }
  game_set_init_position(&game);
  for (int i = 0; i < n; i++) {
    const position_t* pos = game_cur_position(&game);
    int stm = position_get_stm(pos);
    int fm = position_get_full_moves(pos);
    if (line + 4 + MAX_SAN_LEN > 80) {
      fprintf(f, "\n");
      line = 0;
    }
    if (stm == WHITE)
      line += fprintf(f, "%d. ", fm);
    else if (i == 0)
      line += fprintf(f, "%d... ", fm);
    move_to_san_string(moves[i], pos, buf);
    if (line + strlen(buf) + 1 > 80) {
      fprintf(f, "\n");
      line = 0;
    }
    line += fprintf(f, "%s ", buf);
    if (!game_make_move(&game, moves[i])) {
      game_cleanup(&game);
      free(moves);
      return 0;
    }
  }

  const char* end = "*";
  if (game_is_checkmate(&game)) {
    const position_t* pos = game_cur_position(&game);
    if (position_get_stm(pos) == WHITE)
      end = "0-1 {Black mates}";
    else
      end = "1-0 {White mates}";
  } else if (game_is_stalemate(&game)) {
    end = "1/2-1/2 {Stalemate}";
  } else if (game_draw_by_material(&game)) {
    end = "1/2-1/2 {Draw by insufficient material}";
  } else if (game_draw_by_repetition(&game)) {
    end = "1/2-1/2 {Draw by threefold repetition}";
  } else if (game_draw_by_50(&game)) {
    end = "1/2-1/2 {Draw by fifty moves rule}";
  }

  if (line + strlen(end) > 80)
    fprintf(f, "\n");
  fprintf(f, "%s\n", end);

  free(moves);
  game_cleanup(&game);
  return 1;
}


static int append_char(int c,
		       char** buf,
		       size_t* buflen,
		       size_t* index)
{
  assert(index != 0);
  assert(buflen != 0);

  if (*index >= *buflen) {
    size_t sz = MAX_SAN_LEN;
    if (sz < *index + 1)
      sz = *index + 1;
    if (sz < 3 * (*buflen) / 2)
      sz = 3 * (*buflen) / 2;
    void* m = realloc(*buf, sz);
    if (m == 0)
      return 0;
    *buf = (char*)m;
    *buflen = sz;
  }
  (*buf)[*index] = c;
  *index = (*index) + 1;
  return 1;
}


static enum pgn_token_t pgn_token(FILE* f, char** buf, size_t* buflen)
{
  assert(buflen != 0);

  const char* sterminals = "[]()<>.*";
  static const enum pgn_token_t terminals[] = {
    pt_left_bracket,
    pt_right_bracket,
    pt_left_par,
    pt_right_par,
    pt_left_angle,
    pt_right_angle,
    pt_dot,
    pt_asterisk
  };

  // Strip whitespaces and comments.
  size_t n = 0;
  append_char('\0', buf, buflen, &n);
  int c = ' ';
  while (isspace(c)) {
    c = fgetc(f);
    if (c == EOF)
      return pt_eof;
    if (c == '{' || c == ';') {
      int end = (c == '{' ? '}' : ';');
      while (c != end) {
	c = fgetc(f);
	if (c == EOF)
	  return pt_error;
      }
      continue;
    }
  }

  // Self terminating tokens.
  char* s = strchr(sterminals, c);
  if (s != 0) {
    n = 0;
    append_char(c, buf, buflen ,&n);
    append_char('\0', buf, buflen ,&n);
    return terminals[s - sterminals];
  }

  // Strings.
  if (c == '"') {
    append_char(c, buf, buflen ,&n);
    do {
      c = fgetc(f);
      if (c == EOF) {
	append_char(0, buf, buflen ,&n);
	return pt_error;
      }
      if (c == '\\') {
	c = fgetc(f);
	if (c == '\\')
	  append_char('\\', buf, buflen ,&n);
	if (c == '"') {
	  append_char('"', buf, buflen ,&n);
	  continue;
	}
	append_char(c, buf, buflen ,&n);
      }
    } while (c != '"');
    append_char('\0', buf, buflen ,&n);
    return pt_string;
  }

  // NAGs.
  if (c == '$') {
    n = 0;
    append_char('$', buf, buflen ,&n);
    c = fgetc(f);
    while (c != EOF && isdigit(c)) {
      append_char(c, buf, buflen ,&n);
      c = fgetc(f);
    }
    append_char('\0', buf, buflen ,&n);
    if (n < 2)
      return pt_error;
    if (c != EOF)
      ungetc(c, f);
    return pt_nag;
  }

  // Symbols and integers.
  if (isalnum(c)) {
    n = 0;
    int alldigits = (isdigit(c) ? 1 : 0);
    while (c != EOF && (isalnum(c) || strchr("_+#=:-/", c) != 0)) {
      append_char(c, buf, buflen ,&n);
      if (!isdigit(c))
	alldigits = 0;
      c = fgetc(f);
    }
    append_char('\0', buf, buflen ,&n);
    
    if (c != EOF)
      ungetc(c, f);
  
    return (alldigits ? pt_integer : pt_symbol);
  }

  return pt_error;
}


static enum pgn_token_t skip_pgn_tag_pair_section(FILE* f,
						  char** buf,
						  size_t* buflen)
{
  enum pgn_token_t t;

  t = pgn_token(f, buf, buflen);
  if (t == pt_eof)
    return t;
  while (t == pt_left_bracket) {
    if (pgn_token(f, buf, buflen) != pt_symbol ||
	pgn_token(f, buf, buflen) != pt_string ||
	pgn_token(f, buf, buflen) != pt_right_bracket)
      return pt_error;
    t = pgn_token(f, buf, buflen);
  }
  
  if(t == pt_eof)
    return pt_error;
  
  return t;
}



int pgn_read(FILE* f, struct game_t* g)
{
  char* buf = 0;
  size_t buflen = 0;

  enum pgn_token_t t = skip_pgn_tag_pair_section(f, &buf, &buflen);
  if (t == pt_error) {
    free(buf);
    return -1;
  } 
  if (t == pt_eof) {
    free(buf);
    return 0;
  }
  
  // Parse the movetext section.
  game_t game;
  if (!game_init(&game)) {
    free(buf);
    return -1;
  }
  game_set_init_position(&game);
  
  int nesting = 0;
  int first = 1;
  for (;;) {
    if (!first)
      t = pgn_token(f, &buf, &buflen);
    first = 0;

    if (t == pt_integer ||
	t == pt_dot ||
	t == pt_left_angle ||
	t == pt_right_angle ||
	t == pt_nag)
      continue;
    
    if (t == pt_asterisk ||
	t == pt_error ||
	t == pt_eof)
      break;
    if (t == pt_string ||
	t == pt_left_bracket ||
	t == pt_right_bracket) {
      t = pt_error;
      break;
    }
    if (t == pt_left_par) {
      ++nesting;
      continue;
    }
    if (t == pt_right_par) {
      if (nesting == 0) {
	t = pt_error;
	break;
      }
      --nesting;
    }
    
    if (nesting > 0)
      continue;

    // Symbols.
    if (strcmp(buf, "1-0") == 0 ||
	strcmp(buf, "0-1") == 0 ||
	strcmp(buf, "1/2-1/2") == 0)
      break;

    const position_t* pos = game_cur_position(&game);
    move_t m;
    if (!move_from_san_string(buf, pos, &m)) {
      t = pt_error;
      break;
    }
    game_make_move(&game, m);
  }

  if (t != pt_error)
    game_copy(&game, g);
  game_cleanup(&game);
  
  free(buf);
  return (t == pt_error ? -1 : 1);
}
